#!/usr/bin/python
#-*- coding: utf-8 -*-

###########################################################
# Copyright 2011 Daniel 'grindhold' Brendle and Team
#
# This file is part of Kitchen of Awesome.
#
# Kitchen of Awesome is free software: you can redistribute it and/or 
# modify it under the terms of the GNU General Public License 
# as published by the Free Software Foundation, either 
# version 3 of the License, or (at your option) any later 
# version.
#
# Kitchen of Awesome is distributed in the hope that it will be 
# useful, but WITHOUT ANY WARRANTY; without even the implied 
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
# PURPOSE. See the GNU General Public License for more details.
#
# You should have hreceived a copy of the GNU General Public 
# License along with Kitchen of Awesome. 
# If not, see http://www.gnu.org/licenses/.
###########################################################
from datetime import datetime

import pymongo


def setApplicationReference(app):
    global APPLICATION
    APPLICATION = app

APPLICATION = None


"""
KoaObject is the root object for all data saved in Koa
It maps all data to the database mongodb
"""
class KoaObject():
    CON = pymongo.Connection()
    FORBIDDEN_KEYWORDS = ["application","id", "updateCallbacks"]
    CLASSES = {}
    OBJECTSTORE = {}

    """
    registerClass registers a class given as parameter to KoaObject,
    theClass will be needed in order to load an object of this class
    from the database and restore it.
    """
    @classmethod
    def registerClass(cls,newclass):
        KoaObject.CLASSES[newclass.__name__] = newclass

    """
    load loads a KoaObject from the database by it's mongodb ObjectId
    and stores it in the local objectstore.
    If an object by the given ObjectId id exists in the ObjectStore,
    the existing instance will be returned
    """
    @classmethod
    def load(cls,objid):
        if not KoaObject.OBJECTSTORE.has_key(objid):
            res = APPLICATION.getStorage().find_one({'_id':objid})
            assert res is not None, "Object not Known"
            obj = KoaObject.CLASSES[res["_className"]]()
            dbid = res['_id']
            del(res['_id'])
            del(res['_className'])
            obj.__dict__ = res
            obj.id = dbid
            obj.application = APPLICATION
            KoaObject.OBJECTSTORE[objid] = obj
        return KoaObject.OBJECTSTORE[objid]

    """
    unload removes this KoaObject from the ObjectStore
    """
    def unload(self):
        if self.id is not None:
            del(KoaObject.OBJECTSTORE[self.id])
        del(self)

    """
    The Constructor 
    """
    def __init__(self):
        self.id = None
        self.application = APPLICATION
        self.updateCallbacks = []
    
    """
    _validateListTypes recursively validates wheter a list consists of datatypes
    that mongodb can handle.
    """ 
    def _validateListTypes(self,list):
        result=True
        for el in list:
            if type(el) == list and self._validateListTypes(el):
                continue
            if type(el) not in (int,str,float,long,datetime,None, pymongo.objectid.ObjectId):            
                result = False
                break
        return result

    """
    _cleanDict cleans a given dictionary from all datatypes that do not
    belong into the database and returns it
    If this algorithm finds an object, in an attribute "attrib1", it will
    determine the id of the object, store it in "attrib1_id" and then delete
    "attrib1"
    """
    def _cleanDict(self,uncleanDict):
        for keyword in self.FORBIDDEN_KEYWORDS:
            if uncleanDict.has_key(keyword):
                del(uncleanDict[keyword])
        for key, el in uncleanDict.items():
            if type(el) == list:
                if self._validateListTypes(el):
                    continue
            if type(el) not in (int,str,float,long,datetime,None):
                if hasattr(el,'id') and type(el.id) == pymongo.objectid.ObjectId:
                    uncleanDict[key+'_id'] = el.id
                del(uncleanDict[key])
        return uncleanDict

    """
    store stores this object in the database
    """
    def store(self):
        saveDict = {}
        saveDict.update(self.__dict__)
        if self.id is None:
            saveDict['_className'] = self.__class__.__name__
            saveDict = self._cleanDict(saveDict)
            self.id = APPLICATION.getStorage().insert(saveDict)
            KoaObject.OBJECTSTORE[self.id] = self
        else:
            saveDict = self._cleanDict(saveDict)
            APPLICATION.getStorage().update({'_id':self.id},{'$set':saveDict})
        return self.id

    """
    remove this object from the database and/or from RAM
    """

    def __del__(self,pyOnly=False):
        del(KoaObject.OBJECTSTORE[self.id])
        if not pyOnly:
            APPLICATION.getStorage().remove(self.id)

    """
    this behavior checks if the called attribute has a corresponding id stored
    in the object. if yes, it loads the object, returns it and stores it as the attribute
    """
    def __getattr__(self, name):
        if self.__dict__.has_key(name+'_id'):
            self.__dict__[name]=KoaObject.load(self.__dict__[name+'_id'])
            return self.__dict__[name]
        else:
            raise AttributeError()


    """
    add a function to call in the case that the object changed,
    for example to update gui if a value changed
    """
    def addCallback(self, callback):
        if callback not in self.updateCallbacks:
            self.updateCallbacks.append(callback)
    

    """
    removes a callbackfunction from the object
    """
    def removeCallback(self, callback):
        if callback in self.updateCallbacks:
            self.updateCallbacks.remove(callback)
    

    """
    updated() must be called, when the object has changed.
    """
    def updated(self):
        for cb in self.updateCallbacks:
            cb()
